#pragma once

#include <AVUIPropertyName.h>
#include <AVUIPropertyMetadata.h>

namespace AVUI {

class DependencyPropertyKey;
class DependencyObject;
class DependencyPropertyChangedEventArgs;
class Property;
class TypeConverter;
class DependencyProperty;
class TypeManager;

typedef ObjectPtr (*pfnGetValue)(Property* pProperty, Object*);
typedef void (*pfnSetValue)(Property* pProperty, Object* pObject, Object* pValue);

class Property : public Object
{
    friend class Type;
    friend class TypeManager;
public:
    DECLARE_ELEMENT(Property, Object);

    Property(const char* szPropertyName, const Type& propertyType,
             pfnGetValue getValue, pfnSetValue setValue) :
                 m_szPropertyName(szPropertyName), m_propertyType(propertyType), 
                 m_pfnGetValue(getValue), m_pfnSetValue(setValue), m_pPropertyConverter(NULL), m_isContentProperty(false), m_isStaticProperty(false), m_autoCreateProperty(false) { }

    const char* get_PropertyName() const { return m_szPropertyName; }
    const Type& get_PropertyType() const { return m_propertyType; }

    bool get_IsContentProperty() const { return m_isContentProperty; }
    void set_IsContentProperty(bool isContentProperty) { m_isContentProperty = isContentProperty; }

    bool get_IsStaticProperty() const { return m_isStaticProperty; }
    void set_IsStaticProperty(bool isStaticProperty) { m_isStaticProperty = isStaticProperty; }

    bool get_AutoCreateProperty() const { return m_autoCreateProperty; }
    void set_AutoCreateProperty(bool autoCreateProperty) { m_autoCreateProperty = autoCreateProperty; }

    void SetValue(Object* pObjectToSetOn, Object* pObjectValue) { m_pfnSetValue(this, pObjectToSetOn, pObjectValue); }
    ObjectPtr GetValue(Object* pObject) { return m_pfnGetValue(this, pObject); }

    void RegisterPropertyConverter(const TypeConverter* pPropertyConverter) const { m_pPropertyConverter = pPropertyConverter; }
    const TypeConverter* get_PropertyConverter() const { return m_pPropertyConverter; }

private:

    Property(const Property&);

    pfnGetValue m_pfnGetValue;
    pfnSetValue m_pfnSetValue;

    mutable const TypeConverter* m_pPropertyConverter;

    const char* m_szPropertyName;
    const Type& m_propertyType;

    bool m_isContentProperty;
    bool m_isStaticProperty;
    bool m_autoCreateProperty;
};

class SimplePropertyFlags
{
public:
    enum Enum
    {
        None              = 0x0,
        IsContentProperty = 0x1,
        IsStaticProperty =  0x2,
    };
};


class SimpleProperty : public Property
{
public:

    DECLARE_ELEMENT(SimpleProperty, Property);

    SimpleProperty(const char* szPropertyName, const Type& propertyType,
                   unsigned int flags, pfnGetValue getValue, pfnSetValue setValue) : Property(szPropertyName, propertyType, getValue, setValue)
    {
        if(flags & SimplePropertyFlags::IsContentProperty)
        {
            set_IsContentProperty(true);
        }
        if(flags & SimplePropertyFlags::IsStaticProperty)
        {
            set_IsStaticProperty(true);
        }
    }
    ~SimpleProperty() { };
};

class DependencyProperty : public Property
{
public:
    DECLARE_ELEMENT(DependencyProperty, Property);

#pragma region Public Methods   

    /// <summary>Adds another type as an owner of a dependency property that has already been registered.</summary>
    /// <returns>A reference to the original <see cref="T:System.Windows.DependencyProperty" /> identifier that identifies the dependency property. This identifier should be exposed by the adding class as a public static readonly field.</returns>
    /// <param name="ownerType">The type to add as an owner of this dependency property.</param>
    TRefCountedPtr<DependencyProperty> AddOwner(const Type& ownerType) { return AddOwner(ownerType, NULL); }

    /// <summary>Adds another type as an owner of a dependency property that has already been registered, providing dependency property metadata for the dependency property as it will exist on the provided owner type. </summary>
    /// <returns>A reference to the original <see cref="T:System.Windows.DependencyProperty" /> identifier that identifies the dependency property. This identifier should be exposed by the adding class as a public static readonly field.</returns>
    /// <param name="ownerType">The type to add as owner of this dependency property.</param>
    /// <param name="typeMetadata">The metadata that qualifies the dependency property as it exists on the provided type.</param>
    TRefCountedPtr<DependencyProperty> AddOwner(const Type& ownerType, PropertyMetadata* pTypeMetadata);

    /// <summary>Returns the metadata for this dependency property as it exists on a specified existing type. </summary>
    /// <returns>A property metadata object.</returns>
    /// <param name="forType">The specific type from which to retrieve the dependency property metadata.</param>
    PropertyMetadata* GetMetadata(const Type& forType);

    /// <summary>Returns the metadata for this dependency property as it exists on the specified object instance. </summary>
    /// <returns>A property metadata object.</returns>
    /// <param name="dependencyObject">A dependency object that is checked for type, to determine which type-specific version of the dependency property the metadata should come from.</param>
    PropertyMetadata* GetMetadata(DependencyObject* pDependencyObject);

    /// <summary>Determines whether a specified value is acceptable for this dependency property's type, as checked against the property type provided in the original dependency property registration. </summary>
    /// <returns>true if the specified value is the registered property type or an acceptable derived type; otherwise, false.</returns>
    /// <param name="value">The value to check.</param>
    bool IsValidType(Object* pValue) { return IsValidType(pValue, m_pPropertyType); }

    /// <summary>Determines whether the provided value is accepted for the type of property through basic type checking, and also potentially if it is within the allowed range of values for that type. </summary>
    /// <returns>true if the value is acceptable and is of the correct type or a derived type; otherwise, false.</returns>
    /// <param name="value">The value to check.</param>
    bool IsValidValue(Object* pValue);

    /// <summary>Specifies alternate metadata for this dependency property when it is present on instances of a specified type, overriding the metadata that existed for the dependency property as it was inherited from base types.</summary>
    /// <returns />
    /// <param name="forType">The type where this dependency property is inherited and where the provided alternate metadata will be applied.</param>
    /// <param name="typeMetadata">The metadata to apply to the dependency property on the overriding type.</param>
    /// <exception cref="T:System.InvalidOperationException">An attempt was made to override metadata on a read-only dependency property (that operation cannot be done using this signature).</exception>
    /// <exception cref="T:System.ArgumentException">Metadata was already established for the dependency property as it exists on the provided type.</exception>
    void OverrideMetadata(const Type& forType, PropertyMetadata* pTypeMetadata);

    /// <summary>Supplies alternate metadata for a read-only dependency property when it is present on instances of a specified type, overriding the metadata that was provided in the initial dependency property registration. You must pass the <see cref="T:System.Windows.DependencyPropertyKey" /> for the read-only dependency property to avoid raising an exception.</summary>
    /// <returns />
    /// <param name="forType">The type where this dependency property is inherited and where the provided alternate metadata will be applied.</param>
    /// <param name="typeMetadata">The metadata to apply to the dependency property on the overriding type.</param>
    /// <param name="key">The access key for a read-only dependency property. </param>
    void OverrideMetadata(const Type& forType, PropertyMetadata* pTypeMetadata, DependencyPropertyKey* pKey);

    /// <summary>Registers a dependency property with the specified property name, property type, and owner type. </summary>
    /// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
    /// <param name="szName">The name of the dependency property to register. The name must be unique within the registration namespace of the owner type.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    static TRefCountedPtr<DependencyProperty> Register(const char* szName, const Type& propertyType, const Type& ownerType) { return Register(szName, propertyType, ownerType, NULL, NULL); }

    /// <summary>Registers a dependency property with the specified property name, property type, owner type, and property metadata. </summary>
    /// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
    /// <param name="szName">The name of the dependency property to register.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    /// <param name="pTypeMetadata">Property metadata for the dependency property.</param>
    static TRefCountedPtr<DependencyProperty> Register(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pTypeMetadata) { return Register(szName, propertyType, ownerType, pTypeMetadata, NULL); }

    /// <summary>Registers a dependency property with the specified property name, property type, owner type, property metadata, and a value validation callback for the property. </summary>
    /// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
    /// <param name="szName">The name of the dependency property to register.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    /// <param name="typeMetadata">Property metadata for the dependency property.</param>
    /// <param name="validateValueCallback">A reference to a callback that should perform any custom validation of the dependency property value beyond typical type validation.</param>
    static TRefCountedPtr<DependencyProperty> Register(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pTypeMetadata, ValidateValueCallback validateValueCallback);

    /// <summary>Registers an attached property with the specified property name, property type, and owner type. </summary>
    /// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
    /// <param name="szName">The name of the dependency property to register.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    static TRefCountedPtr<DependencyProperty> RegisterAttached(const char* szName, const Type& propertyType, const Type& ownerType) { return RegisterAttached(szName, propertyType, ownerType, NULL, NULL); }

    /// <summary>Registers an attached property with the specified property name, property type, owner type, and property metadata. </summary>
    /// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
    /// <param name="szName">The name of the dependency property to register.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    /// <param name="defaultMetadata">Property metadata for the dependency property. This can include the default value as well as other characteristics.</param>
    static TRefCountedPtr<DependencyProperty> RegisterAttached(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pDefaultMetadata) { return RegisterAttached(szName, propertyType, ownerType, pDefaultMetadata, NULL); }

    /// <summary>Registers an attached property with the specified property type, owner type, property metadata, and value validation callback for the property. </summary>
    /// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
    /// <param name="szName">The name of the dependency property to register.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    /// <param name="defaultMetadata">Property metadata for the dependency property. This can include the default value as well as other characteristics.</param>
    /// <param name="validateValueCallback">A reference to a callback that should perform any custom validation of the dependency property value beyond typical type validation.</param>
    static TRefCountedPtr<DependencyProperty> RegisterAttached(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pDefaultMetadata, ValidateValueCallback validateValueCallback);
 
    /// <summary>Registers a read-only attached property, with the specified property type, owner type, and property metadata. </summary>
    /// <returns>A dependency property key that should be used to set the value of a static read-only field in your class, which is then used to reference the dependency property later.</returns>
    /// <param name="szName">The name of the dependency property to register.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    /// <param name="defaultMetadata">Property metadata for the dependency property.</param>
    static TRefCountedPtr<DependencyPropertyKey> RegisterAttachedReadOnly(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pDefaultMetadata) {  return RegisterAttachedReadOnly(szName, propertyType, ownerType, pDefaultMetadata, NULL); }

    /// <summary>Registers a read-only attached property, with the specified property type, owner type, property metadata, and a validation callback. </summary>
    /// <returns>A dependency property key that should be used to set the value of a static read-only field in your class, which is then used to reference the dependency property.</returns>
    /// <param name="name">The name of the dependency property to register.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    /// <param name="defaultMetadata">Property metadata for the dependency property.</param>
    /// <param name="validateValueCallback">A reference to a user-created callback that should perform any custom validation of the dependency property value beyond typical type validation.</param>
    static TRefCountedPtr<DependencyPropertyKey> RegisterAttachedReadOnly(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pDefaultMetadata, ValidateValueCallback validateValueCallback);

    /// <summary> Registers a read-only dependency property, with the specified property type, owner type, and property metadata. </summary>
    /// <returns>A dependency property key that should be used to set the value of a static read-only field in your class, which is then used to reference the dependency property.</returns>
    /// <param name="szName">The name of the dependency property to register.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    /// <param name="typeMetadata">Property metadata for the dependency property.</param>
    static TRefCountedPtr<DependencyPropertyKey> RegisterReadOnly(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pTypeMetadata) { return RegisterReadOnly(szName, propertyType, ownerType, pTypeMetadata, NULL); }

    /// <summary>Registers a read-only dependency property, with the specified property type, owner type, property metadata, and a validation callback. </summary>
    /// <returns>A dependency property key that should be used to set the value of a static read-only field in your class, which is then used to reference the dependency property later.</returns>
    /// <param name="szName">The name of the dependency property to register.</param>
    /// <param name="propertyType">The type of the property.</param>
    /// <param name="ownerType">The owner type that is registering the dependency property.</param>
    /// <param name="typeMetadata">Property metadata for the dependency property.</param>
    /// <param name="validateValueCallback">A reference to a user-created callback that should perform any custom validation of the dependency property value beyond typical type validation.</param>
    static TRefCountedPtr<DependencyPropertyKey> RegisterReadOnly(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pTypeMetadata, ValidateValueCallback validateValueCallback);

#pragma endregion Public Methods

#pragma region Public Properties

    /// <summary>Gets the default metadata of the dependency property. </summary>
    /// <returns>The default metadata of the dependency property.</returns>
    PropertyMetadata* get_DefaultMetadata() { return m_pDefaultMetadata; }

    /// <summary>Gets an internally generated value that uniquely identifies the dependency property.</summary>
    /// <returns>A unique numeric identifier.</returns>
    int get_GlobalIndex() { return m_packedData & 0xFFFF; }

    /// <summary>Gets the name of the dependency property. </summary>
    /// <returns>The name of the property.</returns>
    String* get_Name() { return m_pName; }
    
    /// <summary>Gets the type of the object that registered the dependency property with the property system, or added itself as owner of the property. </summary>
    /// <returns>The type of the object that registered the property or added itself as owner of the property.</returns>
    const Type* get_OwnerType() { return m_pOwnerType; }

    /// <summary>Gets the type of the object that registered the dependency property with the property system, or added itself as owner of the property. </summary>
    /// <returns>The type of the object that registered the property or added itself as owner of the property.</returns>
    const Type* get_PropertyType() { return m_pPropertyType; }

    /// <summary>Gets a value that indicates whether the dependency property identified by this <see cref="T:System.Windows.DependencyProperty" /> instance is a read-only dependency property.</summary>
    /// <returns>true if the dependency property is read-only; otherwise, false.</returns>
    bool get_ReadOnly() { return m_pReadOnlyKey != NULL; }

    /// <summary>Gets the value validation callback for the dependency property. </summary>
    /// <returns>The value validation callback for this dependency property, as provided for the <paramref name="validateValueCallback" /> parameter in the original dependency property registration.</returns>
    ValidateValueCallback get_ValidateValueCallback() { return m_validateValueCallback; }

    /// <summary>Specifies a static value that is used by the WPF property system rather than null to indicate that the property exists, but does not have its value set by the property system.</summary>
    /// <returns>An unset value. This is effectively the result of a call to the <see cref="T:System.Object" /> constructor.</returns>
    static Object* get_UnsetValue() { static Object unsetValue; return &unsetValue; }

#pragma endregion Public Properties

private:

#pragma region Private Flags Enum

    class Flags
    {
    public:
        enum Enum
        {
            GlobalIndexMask = 0xffff,
            IsDefaultValueChanged = 0x100000,
            IsFreezableType = 0x20000,
            IsObjectType = 0x400000,
            IsPotentiallyInherited = 0x80000,
            IsPotentiallyUsingDefaultValueFactory = 0x200000,
            IsStringType = 0x40000,
            IsValueType = 0x10000
        };
    };

public:

#pragma region Internal Methods

    static DependencyProperty* FromName(String* pName, const Type& ownerType);
    Object* GetDefaultValue(const Type& forType);
    static int GetUniqueGlobalIndex(const Type& ownerType, String* pName);
    static bool IsValidType(Object* pValue, const Type* pPropertyType);
    bool IsValidValueInternal(Object* pValue);
    void ValidateFactoryDefaultValue(Object* pDefaultValue);
    void VerifyReadOnlyKey(DependencyPropertyKey* pCandidateKey);

#pragma endregion Internal Methods

#pragma region Internal Properties

    bool get_IsDefaultValueChanged() { return (m_packedData & Flags::IsDefaultValueChanged) != 0; }
    bool get_IsFreezableType() { return (m_packedData & Flags::IsFreezableType) != 0; }
    bool get_IsObjectType() { return (m_packedData & Flags::IsObjectType) != 0; }
    bool get_IsPotentiallyInherited() { return (m_packedData & Flags::IsPotentiallyInherited) != 0; }
    bool get_IsPotentiallyUsingDefaultValueFactory() { return (m_packedData & Flags::IsPotentiallyUsingDefaultValueFactory) != 0; }
    bool get_IsStringType() { return (m_packedData & Flags::IsStringType) != 0; }
    bool get_IsValueType() { return (m_packedData & Flags::IsValueType) != 0; }    

#pragma endregion Internal Properties

#pragma region Internal Fields

    static ItemStructList< TRefCountedPtr<DependencyProperty> > RegisteredPropertyList;

#pragma region Internal Fields

private:

#pragma region Private Methods

    DependencyProperty(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pDefaultMetadata, ValidateValueCallback validateValueCallback);
    static TRefCountedPtr<Object> AutoGenerateDefaultValue(const Type& propertyType);
    static TRefCountedPtr<PropertyMetadata> AutoGeneratePropertyMetadata(const Type& propertyType, ValidateValueCallback validateValueCallback, const char* szName, const Type& ownerType);
    void ProcessOverrideMetadata(const Type& forType, PropertyMetadata* pTypeMetadata, PropertyMetadata* pBaseMetadata);
    static TRefCountedPtr<DependencyProperty> RegisterCommon(const char* szName, const Type& propertyType, const Type& ownerType, PropertyMetadata* pDefaultMetadata, ValidateValueCallback validateValueCallback);
    static void RegisterParameterValidation(const char* szName, const Type& propertyType, const Type& ownerType);
    void SetupOverrideMetadata(const Type& forType, PropertyMetadata* pTypeMetadata, PropertyMetadata** ppBaseMetadata);
    static void ValidateDefaultValueCommon(Object* pDefaultValue, const Type& propertyType, const char* szPropertyName, ValidateValueCallback validateValueCallback, bool checkThreadAffinity);
    static void ValidateMetadataDefaultValue(PropertyMetadata* pDefaultMetadata, const Type& propertyType, const char* szPropertyName, ValidateValueCallback validateValueCallback);

#pragma endregion Private Methods

#pragma region Private Structures (Extension / WPFG)

struct PropertyMetadataRegistration
{
    PropertyMetadataRegistration() : pType(NULL) { }
    PropertyMetadataRegistration(const Type* pTypeIn, PropertyMetadata* pMetadataIn) : pType(pTypeIn), pMetadata(pMetadataIn) { }

    const Type* pType;
    TRefCountedPtr<PropertyMetadata> pMetadata;
};

#pragma endregion Private Structures (Extension / WPFG)

#pragma region Private Fields

    TRefCountedPtr<PropertyMetadata> m_pDefaultMetadata;
    // Deviation, this is an InsertionSortMap in WPF
    ItemStructList<PropertyMetadataRegistration> m_metadataMap;
    TRefCountedPtr<String> m_pName;
    const Type* m_pOwnerType;
    unsigned int m_packedData;
    const Type* m_pPropertyType;
    TRefCountedPtr<DependencyPropertyKey> m_pReadOnlyKey;
    ValidateValueCallback m_validateValueCallback;
    static int GlobalIndexCount;
    // Deviation, this is a HashTable in WPF, we register DPs on the type object itself
    // ProperyFromName hashtable

#pragma endregion Private Fields

};

class DependencyPropertyKey : public Object
{
public:
    DECLARE_ELEMENT(DependencyPropertyKey, Object);

#pragma region Public Methods

    /// <summary>Overrides the metadata of a read-only dependency property that is represented by this dependency property identifier.</summary>
    /// <returns />
    /// <param name="forType">The type on which this dependency property exists and metadata should be overridden.</param>
    /// <param name="typeMetadata">Metadata supplied for this type.</param>
    /// <exception cref="T:System.InvalidOperationException">Attempted metadata override on a read-write dependency property (cannot be done using this signature).</exception>
    /// <exception cref="T:System.ArgumentException">Metadata was already established for the property as it exists on the provided type.</exception>
    void OverrideMetadata(const Type& forType, PropertyMetadata* pTypeMetadata);

#pragma endregion Public Methods

#pragma region Public Properties

    /// <summary>Gets the dependency property identifier associated with this specialized read-only dependency property identifier. </summary>
    /// <returns>The relevant dependency property identifier.</returns>
    DependencyProperty* get_DependencyProperty() { return m_pDP; }

#pragma endregion Public Properties

public:

#pragma region Internal Methods

    DependencyPropertyKey(DependencyProperty* pDP) : m_pDP(pDP) { }
    void SetDependencyProperty(DependencyProperty* pDP) { m_pDP = pDP; }
 
#pragma endregion Internal Methods

private:

#pragma region Private Fields

    DependencyProperty* m_pDP;

#pragma endregion Private Fields

};


}; // namespace AVUI
